package lumis.report.dailyvisitors;


import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.TemporalType;

import lumis.portal.manager.ManagerFactory;
import lumis.report.IJRDataSourceProvider;
import lumis.report.util.ReportUtil;
import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.data.JRMapCollectionDataSource;

/**
 * Data source provider used for the daily visitors report.
 *
 * @version $Revision: 10458 $ $Date: 2009-06-02 15:49:09 -0300 (Tue, 02 Jun 2009) $
 * @since 4.2.1
 */
public class DailyVisitorsJRDataSourceProvider implements IJRDataSourceProvider
{
	/**
	 * Returns the row in the given list that is for the specified date.
	 * @param rows the rows. The first column is expected to be the startOfPeriod value.
	 * @param date the date desired. 
	 * @return the row of the specified date.
	 * @since 4.2.1
	 */
	private static Object[] getRowDataByDay(List<Object[]> rows, long date)
	{
		for (Object[] row: rows)
		{
			Date startOfPeriod = (Date)row[0];
			long timeDiff = startOfPeriod.getTime() - date;
			if (timeDiff >= 0 && timeDiff < 86400000)
				return row;
		}
		return null;
	}
	
	/**
	 * Reads the data from persistence.
	 * @param startDate the start of the period to be read from. If null
	 * 30 days previous to the current day will be used. The time part of the
	 * date is ignored.
	 * @param endDate the end of the period to be read from. If null the current
	 * day will be used. The time part of the date is ignored.
	 * @param rootChannelPath the path for the root channel of the structure
	 * used for this report.
	 * @param includeEmptyRows if true rows for data with zero values will also
	 * be returned.
	 * @return the data rows.
	 * @since 4.2.1
	 */
	@SuppressWarnings("unchecked")
	private static List<Map<String, Object>> readData(Date startDate, Date endDate, 
			String rootChannelPath, boolean includeEmptyRows) throws JRException
	{
		// default values
		if (startDate == null)
			startDate = new Date(System.currentTimeMillis() - 1000L*60*60*24*30);
		if (endDate == null)
			endDate = new Date(System.currentTimeMillis());
		
		// truncate start and end date
		startDate = ReportUtil.truncateDateTime(startDate);
		endDate = ReportUtil.truncateDateTime(endDate);

		// increment endDate in one day, since the endDate is to be included
		endDate = new Date(endDate.getTime() + 1000L*60*60*24);
		
		// read data from database
		String insideRootChannelPath = rootChannelPath + "/%";
		EntityManager entityManager = ManagerFactory.getEntityManager();
		List<Object[]> pageViews = entityManager.createQuery("select " +
				"min(ed.startOfPeriod) as eventDate, " +
				"SUM(edm.hits) as hits " +
				"from Event e inner join e.eventDatas ed inner join " +
				"ed.measures edm inner join ed.aggregations channelPathDA " +
				"where e.eventKey='lumis.portal.presentation.ev.pageview' and " +
				"edm.measureType.measureTypeKey='lumis.portal.monitor.mt.duration.ms' and " + 
				"ed.startOfPeriod >= :startDate and ed.startOfPeriod < :endDate and " +
				"channelPathDA.aggregationType.aggregationTypeKey='lumis.portal.monitor.at.channel.path' and " +
				"(channelPathDA.value=:rootChannelPath or channelPathDA.value like :insideRootChannelPath) " + 
				"group by year(ed.startOfPeriod), month(ed.startOfPeriod), day(ed.startOfPeriod) " +
				"order by 1")
				.setParameter("startDate", startDate, TemporalType.TIMESTAMP)
				.setParameter("endDate", endDate, TemporalType.TIMESTAMP)
				.setParameter("rootChannelPath", rootChannelPath)
				.setParameter("insideRootChannelPath", insideRootChannelPath)
				.getResultList();
		List<Object[]> sessionsData = entityManager.createQuery("select " +
				"min(ed.startOfPeriod) as eventDate, " +
				"sum(edm.hits) as hits, " +
				"sum(edm.sumOfValues) AS sumDuration " +
				"from Event e inner join e.eventDatas ed inner join " +
				"ed.measures edm inner join ed.aggregations channelPathDA " +
				"where e.eventKey='lumis.portal.presentation.ev.httpsession' and " +
				"edm.measureType.measureTypeKey='lumis.portal.monitor.mt.duration.ms' and " + 
				"ed.startOfPeriod >= :startDate and ed.startOfPeriod < :endDate and " +
				"channelPathDA.aggregationType.aggregationTypeKey='lumis.portal.monitor.at.channel.path' and " +
				"(channelPathDA.value=:rootChannelPath or channelPathDA.value like :insideRootChannelPath) " + 
				"group by year(ed.startOfPeriod), month(ed.startOfPeriod), day(ed.startOfPeriod) " +
				"order by 1")
				.setParameter("startDate", startDate, TemporalType.TIMESTAMP)
				.setParameter("endDate", endDate, TemporalType.TIMESTAMP)
				.setParameter("rootChannelPath", rootChannelPath)
				.setParameter("insideRootChannelPath", insideRootChannelPath)
				.getResultList();
		
		// merge rows data
		List<Map<String, Object>> result = new ArrayList<Map<String, Object>>(100);
		for (long date = startDate.getTime(); date < endDate.getTime(); date += 86400000)
		{
			Object[] pageViewRow = getRowDataByDay(pageViews, date);
			Object[] sessionRow = getRowDataByDay(sessionsData, date);

			if (!includeEmptyRows && pageViewRow == null && sessionRow == null)
				continue;
			
			long numPageViews = 0;
			if (pageViewRow != null)
				numPageViews = ((Number)pageViewRow[1]).longValue();
			
			long numSessions = 0;
			double sumSessionDuration = 0;
			if (sessionRow != null)
			{
				numSessions = ((Number)sessionRow[1]).longValue();
				sumSessionDuration = ((Number)sessionRow[2]).doubleValue();
			}
			
			Map<String, Object> row = new HashMap<String, Object>();
			row.put("eventDate", new Timestamp(date));
			row.put("numPageViews", numPageViews);
			row.put("numSessions", numSessions);
			row.put("sumSessionDuration", sumSessionDuration);
			result.add(row);
		}
		
		return result;
	}
	
	/**
	 * Returns the data source to be used in the table of the report.
	 * @param startDate the start of the period to be read from. If null
	 * 30 days previous to the current day will be used. The time part of the
	 * date is ignored.
	 * @param endDate the end of the period to be read from. If null the current
	 * day will be used. The time part of the date is ignored.
	 * @param rootChannelPath the path for the root channel of the portal
	 * area. Used to filter the data read.
	 * @return the data source.
	 * @since 4.2.1
	 */
	public static JRDataSource getDataForTable(Date startDate, Date endDate, String rootChannelPath) throws JRException
	{
		return new JRMapCollectionDataSource(readData(startDate, endDate, rootChannelPath, false));
	}
	
	/**
	 * Returns the data source to be used in the chart of the report.
	 * @param startDate the start of the period to be read from. If null
	 * 30 days previous to the current day will be used. The time part of the
	 * date is ignored.
	 * @param endDate the end of the period to be read from. If null the current
	 * day will be used. The time part of the date is ignored.
	 * @param rootChannelPath the path for the root channel of the portal
	 * area. Used to filter the data read.
	 * @return the data source.
	 * @since 4.2.1
	 */
	public static JRDataSource getDataForChart(Date startDate, Date endDate, String rootChannelPath) throws JRException
	{
		return new JRMapCollectionDataSource(readData(startDate, endDate, rootChannelPath, true));
	}

	public JRDataSource create(JasperReport jasperreport, Map<String, Object> parameters) throws JRException
	{
		return getDataForTable((Date)parameters.get("startDate"), 
				(Date)parameters.get("endDate"), (String)parameters.get("rootChannelPath"));
	}

	public void dispose(JRDataSource jrdatasource) throws JRException
	{
	}
}
